home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / Pascal / Snippets / PNL Libraries / Libraries / SpriteWorld / Documentation / SpriteWorld 2 FAQ & AT < prev    next >
Text File  |  1996-10-04  |  14KB  |  155 lines

  1. SpriteWorld 2 
  2. Frequently Asked Questions and Advanced Techniques
  3.  
  4. This file assumes you have some familiarity with SpriteWorld 2. It tries to answer some of the questions that may come up when you start actually working with SpriteWorld 2. It also offers some "tips and advanced techniques" -- ideas for less-obvious ways of working with SpriteWorld and extending its capabilities. SpriteWorld users should feel free to offer contributions to this file: questions, with or without the answer, and advanced techniques you've developed yourself.
  5. Contact Karl Bunker at:
  6. KarlBunker@aol.com
  7.  
  8.  
  9. ==================================================
  10. Frequently Asked Questions Department
  11. ==================================================
  12.  
  13. Q:  Why don't you use doubles for the PPC BlitPixie blitter?
  14.  
  15. A: As you may know, reading and writing unaligned doubles is significantly slower than unaligned longs on the 603 and 604 processors. Although SpriteWorld pads the rects it draws to achieve alignment when it's drawing Sprites, it can't do this when it's drawing the large rects used to update the screen during scrolling background animation. So, conceivably, SW could improve the speed of its animation if it were to use doubles if either:  a) it knew it was drawing an aligned rect, or  b) it knew it was running on a 601 machine. I did investigate doing things this way, but I ran into difficulties, and rather than investing any more time and energy on that aspect of SpriteWorld, I decided to simply use longs for all the blitting. Naturally, I may revisit this issue for future versions of SW. Some speed improvement could also probably be achieved by writing the PPC blitter in assembly rather than C. That too may be revisited for future versions. The PPC blitter as it stands is pretty good, IMO -- thanks largely to help and code snippets from Tim Carroll of Apple DTS.
  16.  
  17. =========================
  18.  
  19.  
  20. Q: I'm using Think C, and I'm getting errors like "prototype required for function 'LMGetMBarHeight'", and "Error:   syntax error: 'SInt8  mmuMode;'".
  21.  
  22. A: The problems you're describing all have to do with the fact that you have Think C's "old Headers" installed on your system. The "Headers" refers to the ".h" files Think C uses to interface with the Mac toolbox. These header files have changed since Think C 7 was released, and SpriteWorld 2 was written to use the newer Headers (usually called "universal headers"). It's possible to get SW to compile with the old Headers, but that's not the best solution. It would be a much better idea to install the universal headers on your system, since a lot of other programming libraries you may want to use in the future will require the universal headers. Also it will make it easier to switch to a newer programming environment if you start using the universal headers now.
  23.  
  24. Installing the universal headers in Think C requires two steps: First you have to acquire the header files themselves and copy them to your hard disk. The headers are freely available from Apple, and can be found at various sources on the Internet <ftp://ftpdev.info.apple.com/Developer_Services/Tool_Chest/Interfaces_&_Libraries>, on commercial online services such as AOL, and on develop magazine's CDs. (Individual develop CD's can be purchased from the Apple Developer Catalog; 800-282-2732.) The headers arrive contained in a folder named "CIncludes". You need to copy this into a folder called "Mac #includes", which is located in a folder called either "Think C" or "Symantec C++ for Macintosh", depending on which version of Think C you're using. Thus, the path should be:
  25. Symantec C++ for Macintosh:Mac #includes:CIncludes:
  26. You then need to remove the folder called "Apple #includes" from the "Mac #includes" folder, as this contains the old Headers. 
  27.  
  28. The next step in installing the universal headers is to create a new "MacHeaders" file; this file also resides in the "Mac #includes" folder. To create the universal headers version of MacHeaders, you must "precompile" the source file "Mac #includes.c". If you have Think C 7.0 or later, this file is in the "Mac #includes" folder. If you have Think C 6.x, contact Symantec for a copy of Mac #includes.c. To precompile this file, first open any project with Think C. Then open the source file "Mac #includes.c". Read the comments at the beginning of this file for instructions on setting its flag for the universal headers. With this file as your the front window in Think C, select "Precompile..." under the "Source" menu, and save the result as "MacHeaders", replacing the old "MacHeaders" file. You will then have the universal headers installed.
  29.  
  30.  
  31. ==================================================
  32. Tips and Advanced Techniques Department
  33. ==================================================
  34.  
  35. "Attaching" a data structure to a Sprite:
  36.  
  37. Often, you will need to keep track of more information relating to a Sprite than can be stored in the userData field of a sprite. To do this, you will first make a data structure containing the additional variables. Then you need to "attach" this data structure to your sprite. Essentially, there are two ways ways to do this. 
  38.  
  39. The first, perhaps a little easier to understand, is to put a pointer to a data structure into the userData field of a sprite. You can either declare the data structure as a global, or allocate memory for it as a pointer with NewPtr(). You'll also want to create a data type corresponding to your data structure:
  40. // the typedef
  41. typedef struct MyDataStruct
  42. {
  43.  short  direction;
  44.  Boolean isMoving;
  45. } MyDataStruct, *MyDataStructPtr;
  46.  
  47. // the global variable declaration
  48. MyDataStruct  gMyDataStruct;
  49.  
  50. Then you put the pointer to the structure into the sprite:
  51.  
  52. mySpriteP->userData = (long)&gMyDataStruct;
  53.  
  54. Once this is done, you can access the data structure by way of the SpritePtr:
  55.  
  56. ((MyDataStructPtr)mySpriteP->userData)->direction = kUp;
  57.  
  58.  
  59. The second method is to define a data structure, and make the SpriteRec  (not the SpritePtr) the first element in that structure. 
  60.  
  61. // the typedef
  62. typedef struct MyDataStruct
  63. {
  64.  SpriteRec  mySprite;
  65.  short  direction;
  66.  Boolean isMoving;
  67. } MyDataStruct, *MyDataStructPtr;
  68.  
  69. Once this is done, you can again access the data structure by way of the SpritePtr -- because they both point to the same physical memory address. Because the SpritePtr is the address of the SpriteRec, and your data structure begins with that SpriteRec, the other elements of your data structure will inhabit the memory directly following the memory inhabited by the SpriteRec. To access your data structure in this case, you cast the type of the SpritePtr to the "MyDataStructPtr" type that you defined. When you use this method, you have to create the sprite correctly, so that its SpriteRec will in fact inhabit the memory location you want it to. Again, to make the memory used by the data structure permanent and stationary, you can either declare the structure as a global variable, or allocate some memory with NewPtr(). I'll show the NewPtr() method here:
  70.  
  71.  tempSpritePtr = (MyDataStructPtr)NewPtr(sizeof(MyDataStruct));
  72.  
  73.  err = SWCreateSpriteFromPictResource(
  74.   mySpriteWorldP, // required by SpriteWorld 2
  75.   mySpriteP,  // this can be, but doesn't have to be, a global variable
  76.   tempSpritePtr, // this tells SW to use the pointer memory you allocated
  77.   kSpritePicResID,
  78.   kSpriteMaskResID,
  79.   kNumFrames,
  80.   kFatMask);
  81.  
  82. If the mySpriteP variable is a global, then you'll be able to access the sprite at any time, rather than just in routines like a CollideProc that have the SpritePtr passed to them. In any case, now you can access your data structure any time you can access the SpritePtr, simply by casting the SpritePtr's type:
  83.  
  84. ((MyDataStructPtr)(mySpriteP))->direction = kUp;
  85.  
  86. =========================
  87.  
  88.  
  89. Playing asynchronous sounds:
  90.  
  91. I recommend "Hollywood"; a nicely implemented library for playing asynchronous sounds. It's included on the CD-ROM that comes with Tricks of the Mac Game Programming Gurus (Hayden Books, (800) 428-5331, hayden@hayden.com), and you're free to use it in your projects. As far as I know, the only (legal) way to obtain Hollywood is by purchasing Tricks of the Mac Game Programming Gurus.
  92. An alternative is "Caveman Sound System", a freeware sound library by David Hay <hay@alumni.cs.colorado.edu>. A new version (1.1) is due to be released soon as of this writing. I haven't worked with Caveman myself, but it looks very full-featured and well done. Caveman Sound System should be available from the following ftp sites:
  93. ftp://tornado.rfx.com/pub/info-mac/_Development/_Library/
  94. ftp://ftp.dataplex.net/pub/info-mac/_Development/_Library/
  95. ftp://ftp.pht.com/mirrors/info-mac/_Development/_Library/
  96.  
  97. =========================
  98.  
  99.  
  100. Achieving a 3D effect, part 1 -- making the Sprites that are higher on the screen appear further away by having them drawn behind the Sprites that are lower and "closer":
  101.  
  102. For Sprites that are stationary, this is no problem. Just add the "furthest" Sprites to the Layer (or their Layer to the SpriteWorld) first, and they will be drawn first when SpriteWorld renders the screen image. Sprites (and Layers) are drawn in the order in which they were added, so the later ones will be drawn over the earlier ones when there is an overlap.
  103.  
  104. For Sprites that move, the situation is more complicated. One way to handle this is to sort the Sprites in a Layer after SWProcessSpriteWorld (which updates the positions of all the Sprites) has been called, and before SWAnimateSpriteWorld (which actually draws the Sprites in their new positions) is called. You will want to sort the Sprites in order of their height on the screen, so that the highest Sprites have the earliest positions in the Layer. Most sorting algorithms involve iteratively swapping the positions of a pair of elements in the list being sorted. The SpriteWorld routine SWSwapSprite() can be used to perform this swapping. The sorting algorithms suitable for this situation include selection sort, insertion sort, bubble sort, and Shellsort. Using Quicksort will be more problematical, since Sprites are kept in a linked list rather than an indexed array, but it's probably doable. Naturally, if you have many Sprites in the Layer, you will want to use the fastest sorting algorithm you can.
  105.  
  106. Another situation occurs if you have a single moving Sprite and several stationary Sprites, and you want the moving Sprite to maintain a correct 3D position as it moves among the stationary Sprites. An example would be a Sprite-person moving around in a forest of Sprite-trees. The person will have to change which trees it is drawn behind or in front of, depending on its height on the screen. To handle this situation, first the stationary Sprites must be added to their layer in order of their height, as discussed above. Then the moving Sprite, which is added to the same Layer, can be given a collideProc to be called whenever it "collides" (overlaps) with one of the stationary Sprites. This collideProc would check the height of the moving Sprite relative to the stationary Sprite, and change its position in the Layer accordingly, using SWInsertSpriteBeforeSprite() and SWInsertSpriteAfterSprite():
  107.  
  108. void movingSpriteCollideProc(
  109.      SpritePtr movingSpriteP,
  110.      SpritePtr stationarySpriteP,
  111.      Rect* sectRect)
  112. {
  113.      if ( (movingSpriteP->destFrameRect.bottom >= stationarySpriteP->destFrameRect.bottom && 
  114.           movingSpriteP->prevSpriteP != stationarySpriteP )
  115.      {
  116.           SWRemoveSprite( mySpriteLayerP, movingSpriteP );
  117.           SWInsertSpriteAfterSprite( mySpriteLayerP, movingSpriteP, stationarySpriteP );
  118.      }
  119.      else if ( movingSpriteP->destFrameRect.bottom < stationarySpriteP->destFrameRect.bottom &&
  120.            movingSpriteP->nextSpriteP != stationarySpriteP )
  121.      {
  122.           SWRemoveSprite( mySpriteLayerP, movingSpriteP );
  123.           SWInsertSpriteBeforeSprite( mySpriteLayerP, movingSpriteP, stationarySpriteP );
  124.      }
  125. }
  126.  
  127. In either of the situations discussed above (multiple moving Sprites or a single moving Sprite), it is likely that you will need some kind of 3D collision-detection routine. Otherwise there will be nothing to prevent a Sprite from passing through an overlapping Sprite as it changes its height on the screen.
  128.  
  129. =========================
  130.  
  131.  
  132. Achieving a 3D effect, part 2 -- making Sprites that are higher on the screen to appear further away by having them drawn smaller than Sprites that are lower and "closer":
  133.  
  134. This requires a scaling blitter. At present, SpriteWorld 2 doesn't have a built-in blitter capable of scaling, but one is still available to you: CopyBits. Of course, CopyBits is not as fast as SpriteWorld's "BlitPixie" blitters, especially when it is forced to perform scaling, but it may be adequate to your situation. Also note that you would only have to use CopyBits for the SpriteDrawProc. The OffscreenDrawProc and the ScreenDrawProc could still be BlitPixie. Here is a method for using CopyBits to perform scaling in SpriteWorld:
  135.  
  136. First you must set the Sprite's drawProc to a routine you will include in your code:
  137. SWSetSpriteDrawProc( theScaledSprite, MyScalingDrawProc);
  138. This drawProc will in fact be calling CopyBits to do the actual drawing, but first it will modify the destination rect appropriately:
  139.  
  140. void MyScalingDrawProc(
  141.      FramePtr srcFrameP,
  142.      FramePtr dstFrameP,
  143.      Rect* srcRect,
  144.      Rect* dstRect)
  145. {
  146.      Rect          scaledDestRect;
  147.      
  148.      scaledDestRect = *dstRect;
  149.      ModifyDestRectAccordingToHeight( &scaledDestRect, dstRect->bottom );
  150.      CopyBits((BitMapPtr)srcFrameP->framePix, (BitMapPtr)dstFrameP->framePix,
  151.                     srcRect, &scaledDestRect, transparent, nil);
  152. }
  153. The routine ModifyDestRectAccordingToHeight() would change scaledDestRect (presumably making it smaller) according to the height of the Sprite, as provided by the dstRect->bottom  parameter.
  154.  
  155.